/*
 * Copyright (c) 2024 iSOFT INFRASTRUCTURE SOFTWARE CO., LTD.
 * easyAda is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */


#include <plat/device.h>
#include <arch/utils.h>
#include <arch/scr.h>
#include <stdlib/assert.h>
#include <tools/macros.h>
#include <core/addrspace.h>
#include <core/allocate.h>
#include <core/dprintf.h>
#include <core/pbuddy.h>
#include <uapi/errors.h>

static addrspace_t kspace;
static addrspace_t rsspace;

image_info_t     kernelinfo;
image_info_t     rootserverinfo;
extern pbuddy_t *p__pbuddy;

spinlock_t asid_lock = SPINLOCK_INIT_UNLOCKED;
#define ASID_FULL_FLAG         0xFFFFFFFFU
#define ASID_BITMAP_CELL_SHIFT 5
#define ASID_BITMAP_SIZE       (1 << (SCR_TTBRx_BIT_ASID_WIDTH - ASID_BITMAP_CELL_SHIFT))
static unsigned int asid_bitmap[ASID_BITMAP_SIZE];

addrspace_t *kernel_addrspace(void)
{
    return &kspace;
}

addrspace_t *user_addrspace(void)
{
    return &rsspace;
}

void addrspace_recycle(addrspace_t *as)
{
    (void)page_table_recycle(as->pg, 0);
    as->pg = NULL;
    release_asid(as->asid);
}

static int BOOTONLY region_map(addrspace_t *as, const region_t *r, unsigned int attr)
{
    const region_t *cur = r;
    int             result;

    for (; cur->size > 0; cur++) {
        if (cur->vbase == 0) {
            continue;
        }
        dprintf("region_map->va:%x -> pa:%x ,size:%d\n", cur->vbase, cur->pbase, cur->size);
        result = addrspace_map(as, cur->pbase, cur->vbase, cur->size, attr);
        assert(result == 0);
    }

    return result;
}

void BOOTONLY prepare_addrspace(addrspace_t *as, const image_info_t *image, const region_t *ram, const region_t *dev,
                                unsigned int iskernel_flag)
{
    int result;
    if (iskernel_flag != 0U) {
        dprintf("map kernel ram section!\n");
        (void)region_map(as, ram,
                         map_mk_attr(MAP_LEVEL_EL1, MAP_TYPE_RAM, MAP_AP_KRW_UNA,
                                     MAP_FLAG_SHAREABLE | MAP_FLAG_CACHEABLE | MAP_FLAG_GLOBAL));

        dprintf("map kernel code section!\n");
        dprintf("region_map->va:%x -> pa:%x ,size:%d\n", image->code.vbase, image->code.pbase, image->code.size);
        result =
            addrspace_map(as, image->code.pbase, image->code.vbase, image->code.size,
                          map_mk_attr(MAP_LEVEL_EL1, MAP_TYPE_RAM, MAP_AP_KRO_UNA,
                                      MAP_FLAG_EXECUTABLE | MAP_FLAG_CACHEABLE | MAP_FLAG_GLOBAL | MAP_FLAG_SHAREABLE));
        assert(result == 0);
        dprintf("map kernel rwdata section!\n");
        dprintf("region_map->va:%x -> pa:%x ,size:%d\n", image->rwdata.vbase, image->rwdata.pbase, image->rwdata.size);
        result = addrspace_map(as, image->rwdata.pbase, image->rwdata.vbase, image->rwdata.size,
                               map_mk_attr(MAP_LEVEL_EL1, MAP_TYPE_RAM, MAP_AP_KRW_UNA,
                                           MAP_FLAG_CACHEABLE | MAP_FLAG_GLOBAL | MAP_FLAG_SHAREABLE));
        assert(result == 0);
        if (!dev) {
            return;
        }
        dprintf("map kernel dev section!\n");
        (void)region_map(
            as, dev, map_mk_attr(MAP_LEVEL_EL1, MAP_TYPE_DEVICE, MAP_AP_KRW_URW, MAP_FLAG_SHAREABLE | MAP_FLAG_GLOBAL));
    } else {
        dprintf("map rootserver ram section!\n");
        (void)region_map(as, ram,
                         map_mk_attr(MAP_LEVEL_EL0, MAP_TYPE_RAM, MAP_AP_KRW_URW,
                                     MAP_FLAG_SHAREABLE | MAP_FLAG_CACHEABLE | MAP_FLAG_GLOBAL));

        dprintf("map rootserver rootserver_stack rodata_shared idle idle_stack section!\n");
        dprintf("region_map->va:%x -> pa:%x ,size:%d\n", image->code.vbase, image->code.pbase, image->code.size);
        result =
            addrspace_map(as, image->code.pbase, image->code.vbase, image->code.size,
                          map_mk_attr(MAP_LEVEL_EL0, MAP_TYPE_RAM, MAP_AP_KRW_URW,
                                      MAP_FLAG_EXECUTABLE | MAP_FLAG_CACHEABLE | MAP_FLAG_GLOBAL | MAP_FLAG_SHAREABLE));
        assert(result == 0);

        //todo
        dprintf("map rootserver code section!\n");
        dprintf("region_map->va:%x -> pa:%x ,size:%d\n", CONFIG_ROOTSERVER_ENTRY_ADDR, CONFIG_ROOTSERVER_LOAD_ADDR, CONFIG_RS_SIZE);
        result =
            addrspace_map(as, CONFIG_ROOTSERVER_LOAD_ADDR, CONFIG_ROOTSERVER_ENTRY_ADDR, CONFIG_RS_SIZE,
                          map_mk_attr(MAP_LEVEL_EL0, MAP_TYPE_RAM, MAP_AP_KRW_URW,
                                      MAP_FLAG_EXECUTABLE | MAP_FLAG_CACHEABLE | MAP_FLAG_GLOBAL | MAP_FLAG_SHAREABLE));
        assert(result == 0);

        dprintf("map rootserver rwdata section!\n");
        dprintf("region_map->va:%x -> pa:%x ,size:%d\n", image->rwdata.vbase, image->rwdata.pbase, image->rwdata.size);
        result = addrspace_map(as, image->rwdata.pbase, image->rwdata.vbase, image->rwdata.size,
                               map_mk_attr(MAP_LEVEL_EL0, MAP_TYPE_RAM, MAP_AP_KRW_URW,
                                           MAP_FLAG_CACHEABLE | MAP_FLAG_GLOBAL | MAP_FLAG_SHAREABLE));
        assert(result == 0);

        dprintf("map rootserver dev section!\n");
        (void)region_map(
            as, dev, map_mk_attr(MAP_LEVEL_EL0, MAP_TYPE_DEVICE, MAP_AP_KRW_URW, MAP_FLAG_SHAREABLE | MAP_FLAG_GLOBAL));
    }
}

void BOOTONLY prepare_dmaram(addrspace_t *as, const region_t *ram)
{
    dprintf("map rootserver dev section!\n");
    (void)region_map(as, ram,
                     map_mk_attr(MAP_LEVEL_EL0, MAP_TYPE_DEVICE, MAP_AP_KRW_URW, MAP_FLAG_SHAREABLE | MAP_FLAG_GLOBAL));
}

void BOOTONLY kernel_addrspace_init(void)
{
    extern const region_t paksram[];
    extern const region_t ksram[];
    extern const region_t ksdev[];
    dprintf("start init kernel_addrspace!\n");
    addrspace_init(&kspace);
    dprintf("map page ram section!\n");
    (void)region_map(&kspace, paksram,
                     map_mk_attr(MAP_LEVEL_EL1, MAP_TYPE_RAM, MAP_AP_KRW_UNA,
                                 MAP_FLAG_SHAREABLE | MAP_FLAG_CACHEABLE | MAP_FLAG_GLOBAL));
    prepare_addrspace(&kspace, &kernelinfo, paksram, NULL, 1);
    prepare_addrspace(&kspace, &kernelinfo, ksram, ksdev, 1);
    page_table_active(kspace.pg, 1);
    (void)addrspace_get(&kspace);

#ifdef DISABLE_BUDDY_PAGE_TABLE
    int           count = 0;
    unsigned long addr[CONFIG_MAX_SECS];
    unsigned long size[CONFIG_MAX_SECS];

    for (; count < CONFIG_MAX_SECS; count++) {
        if (!paksram[count].size) {
            break;
        }
        assert(!(paksram[count].size & lowbitsmask(CONFIG_PAGE_SHIFT)));
        addr[count] = paksram[count].vbase;
        size[count] = paksram[count].size >> CONFIG_PAGE_SHIFT;
    }
    p__pbuddy = pbuddy_init((void **)addr, (unsigned int *)size, count);
#endif
    dprintf("end init kernel_addrspace!\n");
}

void BOOTONLY rootserver_addrspace_init(uint64_t *initsect_v, uint64_t *initsect_f)
{
    extern const region_t usram[];
    extern const region_t usdev[];
    dprintf("start init rootsetver_addrspace!\n");
    addrspace_init(&rsspace);
    prepare_addrspace(&rsspace, &rootserverinfo, usram, usdev, 0);
    page_table_active(rsspace.pg, 0);
    (void)addrspace_get(&rsspace);
    int i, j = 0, lbsize;
    for (i = 0; i < MAX_RAM_INFO; i++) {
        if (initsect_v[i] != 0UL) {
            continue;
        }
        if (!usram[j].size) {
            break;
        }
        initsect_v[i] = usram[j].vbase;
        lbsize        = biol1(usram[j].size);
        initsect_f[i] |= FLAG_RAM_USER;
        initsect_f[i] <<= 8;
        initsect_f[i] |= lbsize;
        initsect_f[i] <<= 32;
        j++;
    }
    dprintf("end init rootsetver_addrspace!\n");
}

void BOOTONLY kernel_addrspace_active(void)
{
    page_table_active(kspace.pg, 1);
    (void)addrspace_get(&kspace);
}

void BOOTONLY user_addrspace_active(void)
{
    page_table_active(rsspace.pg, 0);
    (void)addrspace_get(&rsspace);
}

void prepare_thread_addrspace(addrspace_t *des, addrspace_t *src)
{
    extern const region_t usram[];
    extern const region_t usdev[];

    des->pg = NULL;
    addrspace_init(des);
    page_table_copy(des->pg, src->pg, 0);
}

unsigned short alloc_asid(void)
{
    for (int i = 0; i < ASID_BITMAP_SIZE; i++) {
        spin_lock(&asid_lock);
        if (asid_bitmap[i] != ASID_FULL_FLAG) {
            int            bitpos = get_bit0_pos(asid_bitmap[i]);
            unsigned short result = (i << ASID_BITMAP_CELL_SHIFT) + bitpos;
            set_pos_bit1(&asid_bitmap[i], bitpos);
            spin_unlock(&asid_lock);
            return result;
        }
        spin_unlock(&asid_lock);
    }
    dprintf("there are nothing available asid --> use the asid = 0\n");
    return 0;
}

void release_asid(unsigned short asid)
{
    unsigned int asid_bitmap_pos = asid >> ASID_BITMAP_CELL_SHIFT;
    unsigned int bipos           = asid % (1 << ASID_BITMAP_CELL_SHIFT);
    spin_lock(&asid_lock);
    set_pos_bit0(&asid_bitmap[asid_bitmap_pos], bipos);
    spin_unlock(&asid_lock);
}
